﻿using System;
using System.Collections.Generic;
using FunctionalCLR.Core;
using Zove.Compiler;
using Zove.Runtime.InferencingStrategy;

namespace Zove.Runtime
{

    /// <summary>
    /// The instance
    /// </summary>
    public class Instance
    {

        private string className = string.Empty;

        private List<IInferencingStrategy> inferenceStrategies;

        /// <summary>
        /// The instance name
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// The solved factor queue
        /// </summary>
        public Dictionary<string, SolvedFactor> Queue
        {
            get;
            set;
        }

        /// <summary>
        /// The solved factor formula queue
        /// </summary>
        public Dictionary<string, string> Formulas
        {
            get;
            set;
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        private Instance()
        {
            this.Formulas = new Dictionary<string, string>();
            this.Queue = new Dictionary<string, SolvedFactor>();
            this.inferenceStrategies = new List<IInferencingStrategy>();

            this.inferenceStrategies.Add(new BasicInferencingStrategy());
            this.inferenceStrategies.Add(new EquationInferencingStrategy());
            //this.inferenceStrategies.Add(new NonlinearInferencingStrategy());
        }

        /// <summary>
        /// Construtor with a class name
        /// </summary>
        /// <param name="className"></param>
        public Instance(string className)
            : this()
        {
            this.className = className;
            this.model = ZoveDependencyManager.Instance.RuntimeModelLoader.LoadByClassName(className);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="className"></param>
        public Instance(string name, string className)
            : this(className)
        {
            this.Name = name;
        }

        /// <summary>
        /// set the input
        /// </summary>
        /// <param name="factorName"></param>
        /// <param name="value"></param>
        public void Assign(string factorName, string value)
        {
            RuntimeFactor rf = this.Model.GetFactorByName(
                           factorName);
            if (rf == null)
            {
                throw new FactorNotDeclaredException(
                    factorName, "input", this.Model.Name);
            }

            rf.Status = RuntimeFactorStatus.Input;

            string unit = rf.Unit;
            // length = 60 \m3
            int forwardSlash = value.IndexOf('\\');
            if (forwardSlash > 0)
            {
                unit = value.Split('\\')[1];
                if (Utils.IsIdentifier(unit))
                {
                    value = value.Split('\\')[0];
                }
            }

            double result
                = Convert.ToDouble(
                ZoveDependencyManager.Instance.ParserFactory.Evaluate(value, this.Model));
            if (unit != rf.Unit)
            {
                result = ZoveDependencyManager.Instance.UnitConverter.Convert(result, unit, rf.Unit);
            }

            rf.Value = result;
        }

        /// <summary>
        /// Reset all factors' status
        /// </summary>
        public void Reset()
        {
            foreach (RuntimeFactor rf in this.Model.Factors)
            {
                rf.Status = RuntimeFactorStatus.Unknown;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="c"></param>
        public Instance(ZoomClass c)
            : this()
        {
            IClassFinder finder = ZoveDependencyManager.Instance.ClassFinder;
            IClassLoader cl = ZoveDependencyManager.Instance.ClassLoader;

            DefaultRuntimeModelLoader ml = new DefaultRuntimeModelLoader();
            ml.ClassLoader = cl;

            this.Model = ml.LoadByClass(c);
        }

        private RuntimeModel model = null;

        /// <summary>
        /// The runtime model
        /// </summary>
        public RuntimeModel Model
        {
            get
            {
                return this.model;
            }
            set
            {
                this.model = value;
                this.model.Solution = this;
            }
        }

        /// <summary>
        /// Evaluate the instance
        /// </summary>
        public void Evaluate()
        {
            int oldCount = 0;
            do
            {
                oldCount = this.Model.NumberOfAvailableFactors;
                foreach (IInferencingStrategy s in this.inferenceStrategies)
                {
                    s.Infer(this);
                }
            }
            while (this.Model.NumberOfAvailableFactors > oldCount);
        }
    }
}
